﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Security.Cryptography;
using System.Reflection;
using System.IO;
using System.IO.Compression;
using System.Diagnostics;
using System.Windows;
using System.Xml.Serialization;
using System.Collections.ObjectModel;
using System.Windows.Data;
using System.Globalization;
using System.Security;
using System.Runtime.InteropServices;
using System.Windows.Media;

namespace Samcrypt
{

    /* MouseUtilities from:
     * http://blogs.msdn.com/llobo/archive/2006/09/06/Scrolling-Scrollviewer-on-Mouse-Drag-at-the-boundaries.aspx */
    public class MouseUtilities
    {
        [StructLayout(LayoutKind.Sequential)]
        private struct Win32Point
        {
            public Int32 X;
            public Int32 Y;
        };

        [DllImport("user32.dll")]
        private static extern bool GetCursorPos(ref Win32Point pt);

        [DllImport("user32.dll")]
        private static extern bool ScreenToClient(IntPtr hwnd, ref Win32Point pt);

        public static Point GetMousePosition(Visual relativeTo)
        {
            Win32Point mouse = new Win32Point();
            GetCursorPos(ref mouse);

            System.Windows.Interop.HwndSource presentationSource =
                (System.Windows.Interop.HwndSource)PresentationSource.FromVisual(relativeTo);

            ScreenToClient(presentationSource.Handle, ref mouse);

            GeneralTransform transform = relativeTo.TransformToAncestor(presentationSource.RootVisual);

            Point offset = transform.Transform(new Point(0, 0));

            return new Point(mouse.X - offset.X, mouse.Y - offset.Y);
        }


    };


    public class AppModel : INotifyPropertyChanged
    {

        #region overview

        ////// Data //////////
        // Key Hash
        // Text hash (last saved copy)
        // Path
        // Doc Name (generated)
        // Dirty (generated)
        // Previously saved (gen -- if clear text hash is null)
        // Text  [ calc text hash and determin if dirty ]

        ////// Methods //////
        // bool LoadFromFile(path, password)  -- opens and decripts; saves key, clear text hash, and path if sucsessfull
        //                                       [Consider makeing this the constructor]
        //
        // bool SaveToFile()                     saves current text if dirty
        // bool SaveToFile(path)                 saves to new file path (changes current path)
        //
        // ChangePassword(oldPassword, newPassword)   allows new password to be set and new key hash be generated (if oldPassword matches)
        // 

        /////// States //////
        // New document
        //    - password set / not yet set
        // Loaded document previosuly saved
        //    - password changed (not yet saved) [dirty]

        #endregion

        const string versionString = "Samcrypt v0.1.8 IP";
        public string VersionString { get { return versionString; } }

        const string DefaultDocName = "Document";
        const string DefaultDocExtention = ".scrypt";

        readonly string DocCommentFormat =
                                   "\r\n # " +
                                   "\r\n # {0}  -  {1}" +  // VersionString, // Local DateTime
                                   "\r\n # " +  
                                   "\r\n   \r\n" ;     



        public AppModel()
        {
            Path = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\" + DefaultDocName + DefaultDocExtention;
        }
        public void NewDocument()
        {
            
            Document = new Document();
        }


        // OnPageAdded
        public void NewPage()
        {

        }

        // OnPageDeleted
        public void DeletePage(Page p)
        {

        }


        string _path;
        public string Path
        {
            get { return _path; }
            set
            {
                _path = value;
                _name = System.IO.Path.GetFileNameWithoutExtension(_path);
                OnPropertyChanged("Name");
            }
        }

        string _name;
        public string Name
        {
            get { return _name; }
            set // also set when path is set
            {
                Path = System.IO.Path.GetDirectoryName(Path) + "\\" + value + "." + System.IO.Path.GetExtension(Path);
                OnPropertyChanged("Name");
            }
        }






        private Document _document;
        public Document Document
        {
            get { return _document; }
            set
            {
                _document = value;
                _document.Init();

                OnPropertyChanged("Document");
            }
        }

        // TODO: fix
        public bool SavedCopyExists { get { return Document.EditState == EditState.Clean || Document.EditState == EditState.Dirty; } }






        #region Store / Load (from clipbaord, from file)

        public bool Load(SecureString password, DocSource docSource)
        {
            switch (docSource)
            {
                case DocSource.Clipboard:
                    return load(password, new ClipboardReader());

                case DocSource.File:
                    return load(password, new FileStream(Path, FileMode.Open));

                default:
                    throw new Exception("Unkown DocSource: " + docSource.ToString());
            }
        }

        public bool Store(SecureString password, DocSource docSource)
        {
            switch (docSource)
            {
                case DocSource.Clipboard:
                    return store(password, new ClipboardWriter(1024 * 3));

                case DocSource.File:
                    return store(password, new FileStream(this.Path, FileMode.Create));

                default:
                    throw new Exception("Unkown DocSource");
            }
        }


        private bool store(SecureString password, Stream destination)
        {
            return store(password, new StreamWriter(destination));
        }

        private bool store(SecureString password, StreamWriter destination)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(Document));

            // Write comment
            destination.Write(string.Format(DocCommentFormat, versionString, DateTime.Now.ToString()));

            // Write encoded data
            CryptoEncoder cryptoEncoder = new CryptoEncoder(new Base64Encoder(destination, false), password);
            serializer.Serialize(cryptoEncoder, Document);
            cryptoEncoder.Close();

            return true;
        }



        public bool load(SecureString password, Stream sourceStream)
        {
            return load(password, new StreamReader(sourceStream));
        }

        public bool load(SecureString password, StreamReader sourceStream)
        {
            Base64Decoder base64Decoder;
            CryptoDecoder cryptoDecoder;

            try
            {
                base64Decoder = new Base64Decoder(sourceStream);
                cryptoDecoder = new CryptoDecoder(base64Decoder, password);
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error loading document.\n" + ex.Message);
                return false;
            }

            if (cryptoDecoder.Version == 1)
            {
                // Get clear text only (version 1 isn't xml-based)
                StreamReader streamReader = new StreamReader(cryptoDecoder);
                string clearText = streamReader.ReadToEnd();

                // Create single page doc and insert decoded text
                Document = new Document();
                Page p = new Page() { Order = 1 };
                Document.Pages.Add(p);
                p.SetNew();
                p.Text = clearText;
            }
            else
            {
                try
                {
                    XmlSerializer serializer = new XmlSerializer(typeof(Document));
                    Document = (Document)serializer.Deserialize(cryptoDecoder);
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Error loading document.\n" + ex.Message);
                    return false;
                }
            }

            //Set Document clean
            Document.EditState = EditState.Clean;

            return true;
        }
                
        #endregion


        // wait to imp
        public bool ChangePassword(string oldPassword, string newPassword)
        {
            // gen key and hash for oldPass and ensure match
            // gen key and hash for newPass 
            // save file to disk (new keyhash will be set within save funciton if sucsessfull)


            return true;
        }


        public static byte[] ComputeHash(string text)
        {
            byte[] bytes = Encoding.Default.GetBytes(text);
            return new SHA256Managed().ComputeHash(bytes);
        }

        public static bool CompareBytes(byte[] b1, byte[] b2)
        {
            if (b1 == null || b2 == null || b1.Length != b2.Length)
                return false;

            int i = 0;
            while (i < b1.Length && b1[i] == b2[i]) ++i;

            return i == b1.Length;
        }




        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        // Create the OnPropertyChanged method to raise the event
        protected void OnPropertyChanged(string name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }


        #endregion



    }




    #region For Review (to delete)
    public class AppModel2
    {

        static readonly string versionString = "Samcrypt v0.1.7";
        public string VersionString { get { return versionString; } }

        //static readonly string DefaultDocName = "Document";
        //static readonly string DefaultDocExtention = ".scrypt";


        // CryptoTranscoder
        // DataFormatTranscoder -- e.g. binary, base64 [support for comments]

        // CryptoFile
        //   Properties
        //   - FileVersion (i.e.: version of binary crypto wrapper)
        //   - Encoding -- how bytes are encoded (binary, base64, ...)
        //   - Source (file, clipbaord, network)
        //   - Path
        //   - Name (w/o extention; also, may be nameless)
        //   [Consider: last modified, guid]
        //   - Data (payload bytes -- decoded and decrypted)
        //   
        //
        //   Methods
        //   - AutoSave -- persists data in a manner which allows recovery
        //   - Load -- pulls data into memory; decodes, atempts parce and decrypts
        //   - Store -- builds crypto wrapper, encrypts, encodes; pushes data to data store

        // Document
        //   Properties
        //   - CryptoFile
        //   - EditState (rolls up page edit states)
        //   - Title | LastModified | Version
        // Page
        //   Properties
        //   - 

    }

    public enum FileEncoding { Binary, Base64 }
    public enum FileSource { File, Clipbaord }


    public class FileTranscoder
    {
        // Determins encoding (binary or base64)
        // Decodes if needed
        // Parces header for file version
        // Based on file version, uses apropreate class to parce header 
        // Based on source, creates apropreate CryptoFile object
        // 
        public CryptoFile GetCryptoFile(byte[] fileData, FileSource source)
        {
            return null;
        }
    }

    public abstract class CryptoFile
    {
        public abstract int FileVersion { get; set; }
        public abstract FileEncoding FileEncoding { get; set; }
        public abstract FileSource FileSource { get; set; }
        public abstract string Path { get; set; }
        public abstract string Name { get; set; }

        public abstract byte[] Data { get; set; } // decoded and decrypted

        public abstract void AutoSave(string password);
        public abstract void Load(string password);
        public abstract void Store(string password);

        protected byte[] EncodeFileData()
        {
            if (FileEncoding == FileEncoding.Base64)
            {
                // TODO: encode as base64 and prepend commends
                return null;
            }
            else if (FileEncoding == FileEncoding.Binary)
            {
                return Data;
            }

            throw new Exception();
        }

        protected byte[] DecodeFileData(byte[] encodedData)
        {
            // TODO: check if first 4 bytes are magic, if not decode base64
            return null;
        }

        protected byte[] ParseData(byte[] decodedData)
        {
            // TODO: check version (and deal with version), parse header values and set properties
            return null;
        }

    }
    #endregion






}
